<template>
  <div class="app-container">
    <p>调整速度：<el-slider v-model="speed" style="width: 50%" :min="minSpeed" :max="maxSpeed" :marks="marks" @input="onSpeedChanged" /></p>
    <p>暂停：<el-switch v-model="paused" />
      格子索引：<el-switch v-model="draw_grid_index" />
      骰子动画：<el-switch v-model="dice_animate" /></p>
    <canvas id="canvas" width="1000" height="1000">
      您的浏览器不支持 HTML5！！！
    </canvas>
  </div>
</template>

<script>
/*
飞行棋棋盘格子编号说明
-1: 等待起飞
-2: 到达终点
0-51: 普通格子
52-57: 终点路径
1、14、27、40: 起点格子（也有保护其他作用）
9、22、35、48: 保护格子
51、12、25、38: 进入终点路径格子（公式：(起点格子 + 50) % 52）

每一方的起点位置是 0，对应的普通格子分别如下：
红色：1，绿色：14，黄色：27，蓝色：40。
因为每一方起点前的一格不可能走到，所以每一方走到其对应的第 50 格时，下一格就是进入终点路径格子。

判断是否保护格子公式：(普通格子 - 1) % 13 % 8 == 0
判断进入终点路径公式：51 <= 己方位置 <= 56

普通格子编号示意图：
                        11  12  13
                        10  52  14
     红1    红2         9   53  15         绿1    绿2
                        8   54  16
     红3    红4         7   55  17         绿3    绿4
                        6   56  18
0   1   2   3   4   5       57      19  20  21  22  23  24
51  52  53  54  55  56  57      57  56  55  54  53  52  25
50  49  48  47  46  45      57      31  30  29  28  27  26
                        44  56  32
     蓝1    蓝2         43  55  33         黄1    黄2
                        42  54  34
     蓝3    蓝4         41  53  35         黄3    黄4
                        40  52  36
                        39  38  37
*/

import Constants from './const';
import LudoAI from './ai';

export default {
  name: 'Ludo',
  data() {
    return {
      minSpeed: 100,
      maxSpeed: 5000,
      marks: { 100: '快', 5000: '慢' },
      // 画布
      canvas: null,
      graphic: null,

      // 定时器
      interval: null,
      speed: 2000, // 越小越快
      paused: false,
      dice_animate: false,
      draw_grid_index: true,

      // 属性
      dice_continue: false, // 是否可以继续投骰子
      dice_changed: false, // 骰子是否已更换
      current_dice: 0, // 当前骰子点数
      current_side: '', // 当前下棋方
      current_move: {}, // 本次棋子走位
      current_rank: 0, // 当前排名
      chesses: {
        'R': [-1, -1, -1, -1],
        'G': [-1, -1, -1, -1],
        'Y': [-1, -1, -1, -1],
        'B': [-1, -1, -1, -1],
      }, // 棋子位置（-1表示未出发，-2表示已到达终点）
      win_ranks: {
        'R': 0, 'G': 0, 'Y': 0, 'B': 0,
      }, // 已达终点排名
    };
  },
  mounted() {
    // 初始化画布
    this.canvas = document.getElementById('canvas');
    this.graphic = this.canvas.getContext('2d');

    this.init();
  },
  beforeDestroy() {
    clearInterval(this.interval);
  },
  methods: {
    // 调整速度
    onSpeedChanged() {
      clearInterval(this.interval);
      this.interval = setInterval((self) => { self.update(); }, this.speed, this);
    },
    // 初始化
    init() {
      this.graphic.clearRect(0, 0, this.canvas.width, this.canvas.height);

      this.randomSide();
      this.randomDice();

      this.current_rank = 0;
      this.win_ranks = {
        'R': 0, 'G': 0, 'Y': 0, 'B': 0,
      };
      this.chesses = {
        'R': [-1, -1, -1, -1],
        'G': [-1, -1, -1, -1],
        'Y': [-1, -1, -1, -1],
        'B': [-1, -1, -1, -1],
      };

      this.onSpeedChanged();
    },
    randomSide() {
      this.current_side = Constants.SYMBOLS[Math.floor(Math.random() * Constants.SYMBOLS.length)];
    },
    nextSide() {
      do {
        this.current_side = Constants.SYMBOLS[
          (Constants.SYMBOLS.indexOf(this.current_side) + 1) % Constants.SYMBOLS.length];
      } while (this.win_ranks[this.current_side] !== 0);

      console.log(`下一方：${Constants.SIDES[this.current_side]}`);
    },
    randomDice() {
      this.dice_changed = true;
      this.dice_continue = false;
      this.current_dice = Math.floor(Math.random() * 6) + 1;
      console.log(`${Constants.SIDES[this.current_side]} 掷出点数 ${this.current_dice}`);
    },
    canMoveList(side, dice) {
      const chessIndices = [];
      const chesses = this.chesses[side];
      for (let i = 0; i < chesses.length; i++) {
        const pos = chesses[i];
        if (pos === -1) { // 未出发
          if (dice === 6) {
            chessIndices.push(i); // 可以出发
          }
        } else if (pos >= 0) { // 在路上
          if (pos + dice <= Constants.TOTAL_GRIDS - 1) { // 没有超过终点
            chessIndices.push(i); // 可以移动
          }
        }
      }
      return chessIndices;
    },
    // 返回：己方旧位置，己方新位置
    moveChess(side, chess_index, dice) {
      let new_pos = -2;
      const old_pos = this.chesses[side][chess_index];
      if (old_pos === -1) { // 未出发
        if (dice === 6) {
          new_pos = 0; // 出发
        }
      } else if (old_pos >= 0) { // 在路上
        new_pos = old_pos + dice;
        if (new_pos > Constants.TOTAL_GRIDS - 1) { // 超过终点
          new_pos = old_pos; // 不能移动
        } else if (new_pos === Constants.TOTAL_GRIDS - 1) { // 刚好到达终点
          new_pos = -2;
        }
      } else if (old_pos === -2) { // 已到达终点
        return [-2, -2];
      }

      this.chesses[side][chess_index] = new_pos;
      this.current_move = {
        side, chess_index, old_pos, new_pos,
      };
      return [old_pos, new_pos];
    },
    checkChesses(side, pos) {
      const pub_pos = Constants.priPos2PubPos(pos, side);
      if (pos < 0 || pos >= Constants.GRID_POSITIONS.length - 1) { // 不在主路上
        return false;
      } else if (Constants.GUARD_POSITIONS.indexOf(pub_pos) !== -1) { // 保护点
        return false;
      }

      const chesses = [];
      for (const symbol of Constants.SYMBOLS) {
        if (symbol === side) continue; // 跳过自己一方

        for (let i = 0; i < this.chesses[symbol].length; i++) {
          const opponent_pos = this.chesses[symbol][i];
          if (opponent_pos < 0 || opponent_pos >= Constants.GRID_POSITIONS.length - 1) { // 不在主路上
            continue;
          }

          const opponent_pub_pos = Constants.priPos2PubPos(opponent_pos, symbol);
          if (opponent_pub_pos === pub_pos) { // 吃掉对方棋子
            chesses.push([symbol, i]);
            this.chesses[symbol][i] = -1; // 对方棋子回到基地

            this.$message.error(`${Constants.SIDES[side]} 吃掉了 ${Constants.SIDES[symbol]} 的棋子！`);
            console.log(`${Constants.SIDES[side]} 吃掉了 ${Constants.SIDES[symbol]} 的第 ${i} 个棋子`);
          }
        }
      }

      return chesses.length > 0;
    },
    win(side) {
      this.$message.success(`${Constants.SIDES[side]} 胜利！`);

      this.current_rank++;
      this.win_ranks[side] = this.current_rank;
    },
    isSideWin(side) {
      return this.chesses[side].every(pos => pos === -2);
    },
    isAllWin() {
      return Constants.SYMBOLS.reduce(
        (acc, symbol) =>
          acc + (this.chesses[symbol].every(pos =>
            pos === -2) ? 1 : 0), 0) >=
        Constants.SYMBOLS.length - 1;
    },
    // 游戏逻辑控制
    gameLogic() {
      let chess_index = -1;
      const side = Constants.SIDES[this.current_side];
      const chessIndices = this.canMoveList(this.current_side, this.current_dice);
      console.log(`${side} 掷出 ${this.current_dice} ` +
        `可移动棋子列表: ${chessIndices} ` +
        `当前所有棋子位置: ${this.chesses[this.current_side]}`);

      if (chessIndices.length === 0) { // 如果当前方没有棋子可以移动，则跳过
        // this.$message.warning(`${side} 无法移动，跳过回合。`);
        console.log(`${side} 无法移动，跳过回合。`);

        this.nextSide();
        this.randomDice();
        return;
      } else if (chessIndices.length === 1 || // ①如果只有一个棋子可以移动，则自动移动
        // ②如果有多个棋子可以移动，并且它们的坐标一样，则选择第一个可移动棋子去移动
        chessIndices.every((_, i, arr) => i === 0 || arr[i] === arr[i - 1])) {
        chess_index = chessIndices[0];
      } else { // 如果有多个棋子可以移动，并且它们的坐标不一样，则让 AI 判断下棋棋子
        const random_sides = []; // ['R', 'Y']; // 红方和黄方使用随机走棋
        if (random_sides.indexOf(this.current_side) !== -1) {
          chess_index = chessIndices[Math.floor(Math.random() * chessIndices.length)];
        } else {
          chess_index = LudoAI.getBestChess(
            this.current_side,
            this.chesses,
            this.current_dice,
            chessIndices);
          if (chess_index === -1) {
            chess_index = chessIndices[Math.floor(Math.random() * chessIndices.length)];
          }
        }
      }

      const [old_pos, new_pos] = this.moveChess(this.current_side, chess_index, this.current_dice);
      // this.$message.info(`${side} 的第 ${chess_index} 个棋子从 ${old_pos} 移动到 ${new_pos}`);
      console.log(`${side} 的第 ${chess_index} 个棋子` +
        `从 ${old_pos}（${Constants.priPos2PubPos(old_pos, this.current_side, true)}） ` +
        `移动到 ${new_pos}（${Constants.priPos2PubPos(new_pos, this.current_side, true)}）`);

      if (this.checkChesses(this.current_side, new_pos)) {
        this.dice_continue = true; // 吃掉对方棋子可以继续掷骰子
      }

      // 掷出 6 可以继续掷骰子
      if (this.current_dice === 6) {
        console.log(`${side} 掷出 6，可以继续掷骰子`);

        this.dice_continue = true;
      }

      // 如果本次移动到达终点
      if (old_pos !== -2 && new_pos === -2) {
        this.$message.success(`${side} 的第 ${chess_index} 个棋子到达终点！`);
        console.log(`${side} 的第 ${chess_index} 个棋子到达终点！`);

        // 到达终点，并且己方仍未胜利，可以继续掷骰子
        this.dice_continue = !this.isSideWin(this.current_side);
      }

      if (this.isSideWin(this.current_side) &&
        this.win_ranks[this.current_side] === 0) { // 如果己方已胜利，且还未记录排名
        this.win(this.current_side);
        this.nextSide();
        this.randomDice();
        return;
      }

      // 换下一方下棋
      if (!this.dice_continue) {
        this.nextSide();
      }

      this.randomDice();
    },
    // 画出框架
    drawFrame() {
      this.graphic.clearRect(0, 0, this.canvas.width, this.canvas.height);

      // 画出基地
      for (const symbol of Constants.SYMBOLS) {
        this.graphic.fillStyle = Constants.COLORS[symbol];
        this.graphic.fillRect(
          Constants.BASE_POSITIONS[symbol].x,
          Constants.BASE_POSITIONS[symbol].y,
          Constants.BASE_SIZE,
          Constants.BASE_SIZE);
      }

      // 画出格子
      for (const grid of Constants.GRID_POSITIONS) {
        this.graphic.lineWidth = 1;
        this.graphic.strokeStyle = 'lightgrey';
        this.graphic.strokeRect(grid.x, grid.y, Constants.GRID_SIZE, Constants.GRID_SIZE);

        const grid_index = Constants.GRID_POSITIONS.indexOf(grid);
        if (Constants.GUARD_POSITIONS.indexOf(grid_index) >= 0) { // 保护点
          this.graphic.fillStyle = 'grey';
          this.graphic.fillRect(grid.x + 1, grid.y + 1, Constants.GRID_SIZE - 2, Constants.GRID_SIZE - 2);
        }

        if (this.draw_grid_index) {
          this.graphic.fillStyle = 'lightgrey';
          this.graphic.font = '20px Arial';
          this.graphic.textAlign = 'center';
          this.graphic.textBaseline = 'middle';
          this.graphic.fillText(
            grid_index,
            grid.x + Constants.GRID_SIZE / 2,
            grid.y + Constants.GRID_SIZE / 2);
        }
      }

      // 画出颜色通道
      for (const symbol of Constants.SYMBOLS) {
        this.graphic.lineWidth = 1;
        this.graphic.strokeStyle = 'lightgrey';
        this.graphic.fillStyle = Constants.COLORS[symbol];
        for (const grid of Constants.COLOR_TUNNELS[symbol]) {
          this.graphic.fillRect(grid.x, grid.y, Constants.GRID_SIZE, Constants.GRID_SIZE);
          this.graphic.strokeRect(grid.x, grid.y, Constants.GRID_SIZE, Constants.GRID_SIZE);
        }
      }

      // 画出终点
      for (const symbol of Constants.SYMBOLS) {
        const ceps = Constants.COLOR_ENDPOINTS[symbol];
        this.graphic.fillStyle = Constants.COLORS[symbol];
        this.graphic.beginPath();
        this.graphic.moveTo(ceps[0].x, ceps[0].y);
        for (let i = 1; i < ceps.length; i++) {
          this.graphic.lineTo(ceps[i].x, ceps[i].y);
        }
        this.graphic.closePath();
        this.graphic.fill();
      }

      // 画出分割线
      this.graphic.beginPath();
      this.graphic.moveTo(Constants.MAP_SIZE + 20, 0);
      this.graphic.lineTo(Constants.MAP_SIZE + 20, Constants.MAP_SIZE);
      this.graphic.closePath();
      this.graphic.strokeStyle = 'black';
      this.graphic.lineWidth = 2;
      this.graphic.stroke();

      // 画出骰子外框
      this.graphic.lineWidth = 1;
      this.graphic.strokeStyle = 'grey';
      this.graphic.strokeRect(Constants.DICE_X, Constants.DICE_Y, Constants.DICE_SIZE, Constants.DICE_SIZE);
    },
    getChessXY(side, chess_index, pos, default_index) {
      let draw_x = -1;
      let draw_y = -1;
      let grid_index = default_index;
      if (pos === -1) { // 未出发
        const base_grid = Constants.CHESS_BASE_POSITIONS[side][chess_index];
        draw_x = base_grid.x;
        draw_y = base_grid.y;
      } else if (pos >= 0) { // 在路上
        if (pos < Constants.GRID_POSITIONS.length - 1) { // 主路（己方起点前一格不能走到，最多只能走到己方第 50 格）
          const pub_pos = Constants.priPos2PubPos(pos, side); // 己方位置转换为普通格子
          draw_x = Constants.GRID_POSITIONS[pub_pos].x;
          draw_y = Constants.GRID_POSITIONS[pub_pos].y;
          grid_index = draw_x | (draw_y << 16);
        } else if (pos < Constants.TOTAL_GRIDS - 1) { // 颜色通道
          const color_index = Constants.priPos2ClrIdx(pos);
          draw_x = Constants.COLOR_TUNNELS[side][color_index].x;
          draw_y = Constants.COLOR_TUNNELS[side][color_index].y;
          grid_index = draw_x | (draw_y << 16);
        }
      } else if (pos === -2) { // 已到达终点
        const current_tunnel = Constants.COLOR_TUNNELS[side];
        draw_x = current_tunnel[current_tunnel.length - 1].x;
        draw_y = current_tunnel[current_tunnel.length - 1].y;
        grid_index = draw_x | (draw_y << 16);
      }

      return { draw_x, draw_y, grid_index };
    },
    drawChess() {
      const common_index = 'COMMON';
      const chesses = {}; // 需要画的棋子列表
      chesses[common_index] = [];
      for (const symbol of Constants.SYMBOLS) {
        for (let i = 0; i < this.chesses[symbol].length; i++) {
          const pos = this.chesses[symbol][i];
          const { draw_x, draw_y, grid_index } = this.getChessXY(symbol, i, pos, common_index);
          if (this.isSideWin(symbol)) { // 如果己方已胜利，画个奖杯
            this.graphic.fillStyle = 'black';
            this.graphic.font = '20px Arial';
            this.graphic.textAlign = 'center';
            this.graphic.textBaseline = 'middle';
            this.graphic.fillText(
              '🏆' + this.win_ranks[symbol],
              draw_x + Constants.GRID_SIZE / 2,
              draw_y + Constants.GRID_SIZE / 2);
            continue;
          }

          if (!Array.isArray(chesses[grid_index])) {
            chesses[grid_index] = [];
          }

          chesses[grid_index].push({
            moving: false,
            side: symbol,
            index: i,
            x: draw_x + Constants.GRID_SIZE / 2,
            y: draw_y + Constants.GRID_SIZE / 2,
            color: Constants.CHESS_COLORS[symbol],
          });
        }
      }

      // 画出行走轨迹
      let end_pos = -99;
      if (this.current_move.new_pos === -2) { // 已到达终点
        end_pos = Constants.TOTAL_GRIDS - 1;
      } else if (this.current_move.new_pos >= 0) { // 未到达终点
        end_pos = this.current_move.new_pos;
      }
      if (end_pos >= 0) {
        for (let i = this.current_move.old_pos; i < end_pos; i++) {
          const { draw_x, draw_y, grid_index } = this.getChessXY(
            this.current_move.side,
            this.current_move.chess_index,
            i, common_index);

          if (!Array.isArray(chesses[grid_index])) {
            chesses[grid_index] = [];
          }

          chesses[grid_index].push({
            moving: true,
            side: this.current_move.side,
            index: this.current_move.chess_index,
            x: draw_x + Constants.GRID_SIZE / 2,
            y: draw_y + Constants.GRID_SIZE / 2,
            color: Constants.CHESS_MOVE_COLORS[this.current_move.side],
          });
        }
      }

      // 开始画棋子
      const drawChessOutter = (chess) => { // 外框
        this.graphic.strokeStyle = 'black';
        this.graphic.setLineDash(chess.moving ? [2, 2] : []);
        this.graphic.beginPath();
        this.graphic.moveTo(chess.x, chess.y);
        this.graphic.arc(chess.x, chess.y, Constants.GRID_SIZE / 2 - 1, 0, Math.PI * 2);
        this.graphic.closePath();
        this.graphic.stroke();
        this.graphic.setLineDash([]);
      };
      const drawChessInner = (chess, begin_arc, end_arc, font_size, text_content, text_x, text_y) => { // 内部填充
        this.graphic.fillStyle = chess.color;
        this.graphic.beginPath();
        this.graphic.moveTo(chess.x, chess.y);
        this.graphic.arc(chess.x, chess.y, Constants.GRID_SIZE / 2 - 2, begin_arc, end_arc);
        this.graphic.closePath();
        this.graphic.fill();

        this.graphic.strokeStyle = 'white';
        this.graphic.lineWidth = 1;
        this.graphic.font = font_size + 'px sans-serif';
        this.graphic.textAlign = 'center';
        this.graphic.textBaseline = 'middle';
        this.graphic.strokeText(text_content, text_x, text_y);
      };

      for (const grid_index in chesses) {
        if (grid_index === common_index || // 棋子在普通格子
          chesses[grid_index].length === 1) { // 格子只有一个棋子
          for (const chess of chesses[grid_index]) {
            drawChessOutter(chess);
            drawChessInner(chess, 0, Math.PI * 2, 20, chess.index, chess.x, chess.y);
          }
        } else { // 同一坐标有多个棋子
          drawChessOutter(chesses[grid_index][0]);

          // 按下棋方分组
          const sides = chesses[grid_index].reduce((ret, item) => {
            if (ret[item.side]) {
              ret[item.side].count++;
            } else {
              ret[item.side] = Object.assign({ count: 1 }, item);
            }

            return ret;
          }, {});

          if (Object.keys(sides).length === 1) { // 仅有一种颜色，画出它们的索引
            for (let i = 0; i < chesses[grid_index].length; i++) {
              const chess = chesses[grid_index][i];
              const PI2 = Math.PI * 2;
              const count = chesses[grid_index].length;
              const part = PI2 / count;
              const font_size = [0, 20, 12, 10, 8][count];
              drawChessInner(chess, i * part, (i + 1) * part, font_size, chess.index,
                chess.x + Math.cos((i + 1) * part - part / 2) * Constants.GRID_SIZE / 4,
                chess.y + Math.sin((i + 1) * part - part / 2) * Constants.GRID_SIZE / 4);
            }
          } else { // 多种颜色混合，画出每种颜色棋子总数量
            let i = 0;
            for (const side in sides) {
              const chess = sides[side];
              const PI2 = Math.PI * 2;
              const count = Object.keys(sides).length;
              const part = PI2 / count;
              const font_size = [0, 20, 12, 10, 8][count];
              drawChessInner(chess, i * part, (i + 1) * part, font_size, chess.count,
                chess.x + Math.cos((i + 1) * part - part / 2) * Constants.GRID_SIZE / 4,
                chess.y + Math.sin((i + 1) * part - part / 2) * Constants.GRID_SIZE / 4);

              i++;
            }
          }
        }
      }
    },
    async drawMap() {
      // 画出当前下棋颜色方基地外框
      this.drawSideFrame();

      // 画出棋子
      this.drawChess();

      // 画出骰子点数
      if (this.dice_animate && this.dice_changed) { // 点数有改变，做个动画
        this.dice_changed = false;
        await this.drawDiceAnimate();
      }
      this.drawDice(this.current_dice, Constants.DICE_X, Constants.DICE_Y, Constants.DICE_SIZE);
    },
    async drawDiceAnimate() {
      let diceCounter = 0;
      const animate = function() {
        return new Promise(resolve => {
          const diceInterval = setInterval(() => {
            const dice = Math.floor(Math.random() * 6) + 1;
            this.drawDice(dice, Constants.DICE_X, Constants.DICE_Y, Constants.DICE_SIZE);

            diceCounter++;
            if (diceCounter >= 15) {
              clearInterval(diceInterval);
              resolve();
            }
          }, 80);
        });
      };

      await animate.call(this);
    },
    // 画出骰子点数
    drawDice(dice) {
      this.graphic.clearRect(Constants.DICE_X + 1, Constants.DICE_Y + 1, Constants.DICE_SIZE - 2, Constants.DICE_SIZE - 2);

      this.graphic.fillStyle = Constants.CHESS_COLORS[this.current_side];
      this.graphic.font = '88px Arial';
      this.graphic.textAlign = 'center';
      this.graphic.textBaseline = 'middle';
      this.graphic.fillText(dice, Constants.DICE_X + Constants.DICE_SIZE / 2, Constants.DICE_Y + Constants.DICE_SIZE / 2);
    },
    // 画出当前下棋方基地外框
    drawSideFrame() {
      this.graphic.lineWidth = 3;
      this.graphic.strokeStyle = 'black';
      this.graphic.strokeRect(
        Constants.BASE_POSITIONS[this.current_side].x + 3,
        Constants.BASE_POSITIONS[this.current_side].y + 3,
        Constants.BASE_SIZE - 6,
        Constants.BASE_SIZE - 6);
    },
    // 更新游戏状态
    update() {
      if (this.paused) return;

      // 绘制棋盘
      this.drawFrame();
      // 绘制当前局面
      this.drawMap();
      // 游戏逻辑更新代码
      this.gameLogic();

      // 判断是否全部胜利
      if (this.isAllWin()) {
        let results = '';
        for (const symbol of Constants.SYMBOLS) {
          if (this.win_ranks[symbol] === 0) {
            results += `${Constants.SIDES[symbol]}: 第 ${Constants.SYMBOLS.length} 名\n`;
          } else {
            results += `${Constants.SIDES[symbol]}: 第 ${this.win_ranks[symbol]} 名\n`;
          }
        }

        clearInterval(this.interval);
        this.interval = null;

        this.$msgbox({
          message: '游戏结束！\n' + results,
          callback: () => this.init(),
        });
      }
    },
  },
};

</script>
